﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using NewsMine.Utilities;

namespace NewsMine.Storage
{
    /// <summary>
    /// todo: this needs to be enhanced by using the sqlite instead of the files on the file system.
    /// because file system is very slow in terms of accessing the files.
    /// </summary>


    public class QueueOnFileSystem : IPersistedQueue
    {
        private string queueDirectoryName;

        object syncLock = new object();

        Queue<string> fileNamesQueue = new Queue<string>();
        object queueSyncLock = new object();
        Queue<string> FileNamesQueue
        {
            get
            {
                lock (queueSyncLock)
                {
                    return fileNamesQueue;
                }
            }
        }

        public QueueOnFileSystem()
        {
        }

        public QueueOnFileSystem(string queueDirectoryName)
        {
            this.queueDirectoryName = queueDirectoryName;
        }

        public void Enqueue(object obj)
        {
            lock (syncLock)
            {
                string newFile = Guid.NewGuid().ToString("N");

                string tempFile = Path.Combine(queueDirectoryName, newFile + ".tmp");

                WriteObjectToFile(tempFile, obj);

                string fileName = Path.Combine(queueDirectoryName, DateTime.UtcNow.Ticks.ToString() + "_" + newFile + ".Que");

                File.Move(tempFile, fileName);

                FileNamesQueue.Enqueue(fileName);
            }
        }

        private void WriteObjectToFile(string fileName, object obj)
        {

            Utilities.FileExetensions.SerializeObjectToFile(fileName, obj);
        }

        public void EnqueueMultiple(object[] objects)
        {
            throw new NotImplementedException("enque multiples method at all used... so, not yet implemeted. try using enque");

            //foreach (var obj in objects)
            //{
            //    Enqueue(obj);
            //}
        }

        [Obsolete("new method is implemeted... better used other one.")]
        public object DequeueOld()
        {
            string newFileName = null;
            lock (syncLock)
            {
                try
                {
                    using (FolderSyncLocker folderLocker = new FolderSyncLocker(queueDirectoryName))
                    {
                        var files = Directory.EnumerateFiles(queueDirectoryName, "*.Que");

                        if (files.Count() == 0)
                            return null;
                        Object objFromFile = null;

                        foreach (var file in files)
                        {
                            if (file != null)
                            {
                                if (File.Exists(file) == false)
                                    continue;

                                newFileName = Path.Combine(queueDirectoryName, Path.GetFileName(file) + ".prg");

                                File.Move(file, newFileName);

                                objFromFile = Utilities.FileExetensions.DeserializeFromFile(newFileName);

                                File.Delete(newFileName);
                                break;
                            }
                        }

                        return objFromFile;
                    }
                }
                catch (Exception ex)
                {
                    //if there is an exception, we need to handle here, to avoid distrubance to other queue items
                    TempLogger.Log("There is unexpected error while deque operation on file queue", ex);
                    return null;

                }
            }
        }

        public object Dequeue()
        {
            string newFileName = null;
            lock (syncLock)
            {
                try
                {
                    using (FolderSyncLocker folderLocker = new FolderSyncLocker(queueDirectoryName))
                    {
                        MakeSureFilesQueueIsUpToDate();

                        if (FileNamesQueue.Count == 0)
                            return null;

                        Object objFromFile = null;

                        string file = null;
                        while(FileNamesQueue.Count>0)
                        {
                            file = FileNamesQueue.Dequeue();

                            if (file != null)
                            {
                                if (File.Exists(file) == false)
                                    continue;

                                newFileName = Path.Combine(queueDirectoryName, Path.GetFileName(file) + ".prg");

                                File.Move(file, newFileName);

                                objFromFile = Utilities.FileExetensions.DeserializeFromFile(newFileName);

                                File.Delete(newFileName);
                                break;
                            }
                            else
                                continue;
                        }

                        return objFromFile;
                    }
                }
                catch (Exception ex)
                {
                    //if there is an exception, we need to handle here, to avoid distrubance to other queue items
                    TempLogger.Log("There is unexpected error while deque operation on file queue", ex);
                    return null;

                }
            }
        }

        //todo: this methods needs to be repaired.
        public object[] DequeueMultiple(int count)
        {
            List<object> objectsFromFiles = new List<object>();

            using (FolderSyncLocker folderLocker = new FolderSyncLocker(queueDirectoryName))
            {
                lock (syncLock)
                {
                    //var files = Directory.GetFiles(queueDirectoryName);

                    //if (files.Length == 0)
                    //    return null;

                    //int availableFilesCount = files.Length;

                    //for (int i = 0; i < count && i < availableFilesCount; i++)
                    //{

                    //    object objFromFile = Utilities.FileExetensions.DeserializeFromFile(files[i]);

                    //    File.Delete(files[i]);

                    //    objectsFromFiles.Add(objectsFromFiles);
                    //}

                    MakeSureFilesQueueIsUpToDate();

                    if (FileNamesQueue.Count == 0)
                        return null;
                    string tempFile = string.Empty;

                    for (int i = 0; i < count && i < FileNamesQueue.Count; i++)
                    {
                        tempFile = FileNamesQueue.Dequeue();
                        object objFromFile = Utilities.FileExetensions.DeserializeFromFile(tempFile);

                        File.Delete(tempFile);


                        objectsFromFiles.Add(objectsFromFiles);

                        if (i == count - 1)
                            break;
                    }
                }
            }
            return objectsFromFiles.ToArray();
        }

        public void ClearQueue()
        {
            using (FolderSyncLocker folderLocker = new FolderSyncLocker(queueDirectoryName))
            {
                lock (syncLock)
                {
                    string[] filePaths = Directory.GetFiles(queueDirectoryName, "*.Que");

                    foreach (string filePath in filePaths)
                    {
                        try
                        {
                            File.Delete(filePath);
                        }
                        catch (IOException ioException)
                        {
                            //todo: need to handle the exception gracefully.
                        }
                    }
                    FileNamesQueue.Clear();
                }
            }
        }

        public int GetCount()
        {
            lock (syncLock)
            {
                MakeSureFilesQueueIsUpToDate();
                return FileNamesQueue.Count;
                //var files = Directory.EnumerateFiles(queueDirectoryName, "*.Que");
                //return files.Count();
            }
        }

        private void MakeSureFilesQueueIsUpToDate()
        {
            if (FileNamesQueue.Count > 0)
                return;

            var files = Directory.GetFiles(queueDirectoryName, "*.Que");

            if (files.Count() == 0)
                return;

            files = (from f in files orderby f select f).ToArray();

            foreach (var file in files)
            {
                if (string.IsNullOrEmpty(file))
                    continue;

                if (File.Exists(file) == false)
                    continue;

                FileNamesQueue.Enqueue(file);
            }
        }

    }
}
